home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / Miro_Downloader.exe / pstats.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2007-11-12  |  22.2 KB  |  740 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.5)
  3.  
  4. '''Class for printing reports on profiled python code.'''
  5. import sys
  6. import os
  7. import time
  8. import marshal
  9. import re
  10. __all__ = [
  11.     'Stats']
  12.  
  13. class Stats:
  14.     '''This class is used for creating reports from data generated by the
  15.     Profile class.  It is a "friend" of that class, and imports data either
  16.     by direct access to members of Profile class, or by reading in a dictionary
  17.     that was emitted (via marshal) from the Profile class.
  18.  
  19.     The big change from the previous Profiler (in terms of raw functionality)
  20.     is that an "add()" method has been provided to combine Stats from
  21.     several distinct profile runs.  Both the constructor and the add()
  22.     method now take arbitrarily many file names as arguments.
  23.  
  24.     All the print methods now take an argument that indicates how many lines
  25.     to print.  If the arg is a floating point number between 0 and 1.0, then
  26.     it is taken as a decimal percentage of the available lines to be printed
  27.     (e.g., .1 means print 10% of all available lines).  If it is an integer,
  28.     it is taken to mean the number of lines of data that you wish to have
  29.     printed.
  30.  
  31.     The sort_stats() method now processes some additional options (i.e., in
  32.     addition to the old -1, 0, 1, or 2).  It takes an arbitrary number of
  33.     quoted strings to select the sort order.  For example sort_stats(\'time\',
  34.     \'name\') sorts on the major key of \'internal function time\', and on the
  35.     minor key of \'the name of the function\'.  Look at the two tables in
  36.     sort_stats() and get_sort_arg_defs(self) for more examples.
  37.  
  38.     All methods return self,  so you can string together commands like:
  39.         Stats(\'foo\', \'goo\').strip_dirs().sort_stats(\'calls\').                            print_stats(5).print_callers(5)
  40.     '''
  41.     
  42.     def __init__(self, *args, **kwds):
  43.         self.stream = sys.stdout
  44.         if 'stream' in kwds:
  45.             self.stream = kwds['stream']
  46.             del kwds['stream']
  47.         
  48.         if not len(args):
  49.             arg = None
  50.         else:
  51.             arg = args[0]
  52.             args = args[1:]
  53.         self.init(arg)
  54.         self.add(*args)
  55.  
  56.     
  57.     def init(self, arg):
  58.         self.all_callees = None
  59.         self.files = []
  60.         self.fcn_list = None
  61.         self.total_tt = 0
  62.         self.total_calls = 0
  63.         self.prim_calls = 0
  64.         self.max_name_len = 0
  65.         self.top_level = { }
  66.         self.stats = { }
  67.         self.sort_arg_dict = { }
  68.         self.load_stats(arg)
  69.         trouble = 1
  70.         
  71.         try:
  72.             self.get_top_level_stats()
  73.             trouble = 0
  74.         finally:
  75.             pass
  76.  
  77.  
  78.     
  79.     def load_stats(self, arg):
  80.         if not arg:
  81.             self.stats = { }
  82.         elif type(arg) == type(''):
  83.             f = open(arg, 'rb')
  84.             self.stats = marshal.load(f)
  85.             f.close()
  86.             
  87.             try:
  88.                 file_stats = os.stat(arg)
  89.                 arg = time.ctime(file_stats.st_mtime) + '    ' + arg
  90.             except:
  91.                 pass
  92.  
  93.             self.files = [
  94.                 arg]
  95.         elif hasattr(arg, 'create_stats'):
  96.             arg.create_stats()
  97.             self.stats = arg.stats
  98.             arg.stats = { }
  99.         
  100.         if not self.stats:
  101.             raise TypeError, "Cannot create or construct a %r object from '%r''" % (self.__class__, arg)
  102.         
  103.  
  104.     
  105.     def get_top_level_stats(self):
  106.         for cc, nc, tt, ct, callers in self.stats.items():
  107.             self.total_calls += nc
  108.             self.prim_calls += cc
  109.             self.total_tt += tt
  110.             if len(func_std_string(func)) > self.max_name_len:
  111.                 self.max_name_len = len(func_std_string(func))
  112.                 continue
  113.             self if callers.has_key(('jprofile', 0, 'profiler')) else self
  114.         
  115.  
  116.     
  117.     def add(self, *arg_list):
  118.         if not arg_list:
  119.             return self
  120.         
  121.         if len(arg_list) > 1:
  122.             self.add(*arg_list[1:])
  123.         
  124.         other = arg_list[0]
  125.         if type(self) != type(other) or self.__class__ != other.__class__:
  126.             other = Stats(other)
  127.         
  128.         self.files += other.files
  129.         self.total_calls += other.total_calls
  130.         self.prim_calls += other.prim_calls
  131.         self.total_tt += other.total_tt
  132.         for func in other.top_level:
  133.             self.top_level[func] = None
  134.         
  135.         self.fcn_list = None
  136.         for func, stat in other.stats.iteritems():
  137.             self.stats[func] = add_func_stats(old_func_stat, stat)
  138.         
  139.         return self
  140.  
  141.     
  142.     def dump_stats(self, filename):
  143.         '''Write the profile data to a file we know how to load back.'''
  144.         f = file(filename, 'wb')
  145.         
  146.         try:
  147.             marshal.dump(self.stats, f)
  148.         finally:
  149.             f.close()
  150.  
  151.  
  152.     sort_arg_dict_default = {
  153.         'calls': (((1, -1),), 'call count'),
  154.         'cumulative': (((3, -1),), 'cumulative time'),
  155.         'file': (((4, 1),), 'file name'),
  156.         'line': (((5, 1),), 'line number'),
  157.         'module': (((4, 1),), 'file name'),
  158.         'name': (((6, 1),), 'function name'),
  159.         'nfl': (((6, 1), (4, 1), (5, 1)), 'name/file/line'),
  160.         'pcalls': (((0, -1),), 'call count'),
  161.         'stdname': (((7, 1),), 'standard name'),
  162.         'time': (((2, -1),), 'internal time') }
  163.     
  164.     def get_sort_arg_defs(self):
  165.         '''Expand all abbreviations that are unique.'''
  166.         if not self.sort_arg_dict:
  167.             self.sort_arg_dict = dict = { }
  168.             bad_list = { }
  169.             for word, tup in self.sort_arg_dict_default.iteritems():
  170.                 fragment = word
  171.                 while fragment:
  172.                     if not fragment:
  173.                         break
  174.                     
  175.                     if fragment in dict:
  176.                         bad_list[fragment] = 0
  177.                         break
  178.                     
  179.                     dict[fragment] = tup
  180.                     fragment = fragment[:-1]
  181.             
  182.             for word in bad_list:
  183.                 del dict[word]
  184.             
  185.         
  186.         return self.sort_arg_dict
  187.  
  188.     
  189.     def sort_stats(self, *field):
  190.         if not field:
  191.             self.fcn_list = 0
  192.             return self
  193.         
  194.         if len(field) == 1 and type(field[0]) == type(1):
  195.             field = [
  196.                 {
  197.                     -1: 'stdname',
  198.                     0: 'calls',
  199.                     1: 'time',
  200.                     2: 'cumulative' }[field[0]]]
  201.         
  202.         sort_arg_defs = self.get_sort_arg_defs()
  203.         sort_tuple = ()
  204.         self.sort_type = ''
  205.         connector = ''
  206.         for word in field:
  207.             sort_tuple = sort_tuple + sort_arg_defs[word][0]
  208.             self.sort_type += connector + sort_arg_defs[word][1]
  209.             connector = ', '
  210.         
  211.         stats_list = []
  212.         for cc, nc, tt, ct, callers in self.stats.iteritems():
  213.             stats_list.append((cc, nc, tt, ct) + func + (func_std_string(func), func))
  214.         
  215.         stats_list.sort(TupleComp(sort_tuple).compare)
  216.         self.fcn_list = fcn_list = []
  217.         for tuple in stats_list:
  218.             fcn_list.append(tuple[-1])
  219.         
  220.         return self
  221.  
  222.     
  223.     def reverse_order(self):
  224.         if self.fcn_list:
  225.             self.fcn_list.reverse()
  226.         
  227.         return self
  228.  
  229.     
  230.     def strip_dirs(self):
  231.         oldstats = self.stats
  232.         self.stats = newstats = { }
  233.         max_name_len = 0
  234.         for cc, nc, tt, ct, callers in oldstats.iteritems():
  235.             newfunc = func_strip_path(func)
  236.             if len(func_std_string(newfunc)) > max_name_len:
  237.                 max_name_len = len(func_std_string(newfunc))
  238.             
  239.             newcallers = { }
  240.             for func2, caller in callers.iteritems():
  241.                 newcallers[func_strip_path(func2)] = caller
  242.             
  243.             if newfunc in newstats:
  244.                 newstats[newfunc] = add_func_stats(newstats[newfunc], (cc, nc, tt, ct, newcallers))
  245.                 continue
  246.             newstats[newfunc] = (cc, nc, tt, ct, newcallers)
  247.         
  248.         old_top = self.top_level
  249.         self.top_level = new_top = { }
  250.         for func in old_top:
  251.             new_top[func_strip_path(func)] = None
  252.         
  253.         self.max_name_len = max_name_len
  254.         self.fcn_list = None
  255.         self.all_callees = None
  256.         return self
  257.  
  258.     
  259.     def calc_callees(self):
  260.         if self.all_callees:
  261.             return None
  262.         
  263.         self.all_callees = all_callees = { }
  264.         for cc, nc, tt, ct, callers in self.stats.iteritems():
  265.             if func not in all_callees:
  266.                 all_callees[func] = { }
  267.             
  268.             for func2, caller in callers.iteritems():
  269.                 if func2 not in all_callees:
  270.                     all_callees[func2] = { }
  271.                 
  272.                 all_callees[func2][func] = caller
  273.             
  274.         
  275.  
  276.     
  277.     def eval_print_amount(self, sel, list, msg):
  278.         new_list = list
  279.         if type(sel) == type(''):
  280.             new_list = []
  281.             for func in list:
  282.                 if re.search(sel, func_std_string(func)):
  283.                     new_list.append(func)
  284.                     continue
  285.             
  286.         else:
  287.             count = len(list)
  288.             if type(sel) == type(1):
  289.                 if sel <= sel:
  290.                     pass
  291.                 elif sel < 1:
  292.                     count = int(count * sel + 0.5)
  293.                     new_list = list[:count]
  294.                 elif type(sel) == type(1):
  295.                     if sel <= sel:
  296.                         pass
  297.                     elif sel < count:
  298.                         count = sel
  299.                         new_list = list[:count]
  300.                     
  301.         if len(list) != len(new_list):
  302.             msg = msg + '   List reduced from %r to %r due to restriction <%r>\n' % (len(list), len(new_list), sel)
  303.         
  304.         return (new_list, msg)
  305.  
  306.     
  307.     def get_print_list(self, sel_list):
  308.         width = self.max_name_len
  309.         if self.fcn_list:
  310.             list = self.fcn_list[:]
  311.             msg = '   Ordered by: ' + self.sort_type + '\n'
  312.         else:
  313.             list = self.stats.keys()
  314.             msg = '   Random listing order was used\n'
  315.         for selection in sel_list:
  316.             (list, msg) = self.eval_print_amount(selection, list, msg)
  317.         
  318.         count = len(list)
  319.         if not list:
  320.             return (0, list)
  321.         
  322.         print >>self.stream, msg
  323.         if count < len(self.stats):
  324.             width = 0
  325.             for func in list:
  326.                 if len(func_std_string(func)) > width:
  327.                     width = len(func_std_string(func))
  328.                     continue
  329.             
  330.         
  331.         return (width + 2, list)
  332.  
  333.     
  334.     def print_stats(self, *amount):
  335.         for filename in self.files:
  336.             print >>self.stream, filename
  337.         
  338.         if self.files:
  339.             print >>self.stream
  340.         
  341.         indent = '        '
  342.         for func in self.top_level:
  343.             print >>self.stream, indent, func_get_function_name(func)
  344.         
  345.         print >>self.stream, indent, self.total_calls, 'function calls',
  346.         if self.total_calls != self.prim_calls:
  347.             print >>self.stream, '(%d primitive calls)' % self.prim_calls,
  348.         
  349.         print >>self.stream, 'in %.3f CPU seconds' % self.total_tt
  350.         print >>self.stream
  351.         (width, list) = self.get_print_list(amount)
  352.         if list:
  353.             self.print_title()
  354.             for func in list:
  355.                 self.print_line(func)
  356.             
  357.             print >>self.stream
  358.             print >>self.stream
  359.         
  360.         return self
  361.  
  362.     
  363.     def print_callees(self, *amount):
  364.         (width, list) = self.get_print_list(amount)
  365.         if list:
  366.             self.calc_callees()
  367.             self.print_call_heading(width, 'called...')
  368.             for func in list:
  369.                 if func in self.all_callees:
  370.                     self.print_call_line(width, func, self.all_callees[func])
  371.                     continue
  372.                 self.print_call_line(width, func, { })
  373.             
  374.             print >>self.stream
  375.             print >>self.stream
  376.         
  377.         return self
  378.  
  379.     
  380.     def print_callers(self, *amount):
  381.         (width, list) = self.get_print_list(amount)
  382.         if list:
  383.             self.print_call_heading(width, 'was called by...')
  384.             for func in list:
  385.                 (cc, nc, tt, ct, callers) = self.stats[func]
  386.                 self.print_call_line(width, func, callers, '<-')
  387.             
  388.             print >>self.stream
  389.             print >>self.stream
  390.         
  391.         return self
  392.  
  393.     
  394.     def print_call_heading(self, name_size, column_title):
  395.         print >>self.stream, 'Function '.ljust(name_size) + column_title
  396.         subheader = False
  397.         for cc, nc, tt, ct, callers in self.stats.itervalues():
  398.             if callers:
  399.                 value = callers.itervalues().next()
  400.                 subheader = isinstance(value, tuple)
  401.                 break
  402.                 continue
  403.         
  404.         if subheader:
  405.             print >>self.stream, ' ' * name_size + '    ncalls  tottime  cumtime'
  406.         
  407.  
  408.     
  409.     def print_call_line(self, name_size, source, call_dict, arrow = '->'):
  410.         print >>self.stream, func_std_string(source).ljust(name_size) + arrow,
  411.         if not call_dict:
  412.             print >>self.stream
  413.             return None
  414.         
  415.         clist = call_dict.keys()
  416.         clist.sort()
  417.         indent = ''
  418.         for func in clist:
  419.             name = func_std_string(func)
  420.             value = call_dict[func]
  421.             if isinstance(value, tuple):
  422.                 (nc, cc, tt, ct) = value
  423.                 if nc != cc:
  424.                     substats = '%d/%d' % (nc, cc)
  425.                 else:
  426.                     substats = '%d' % (nc,)
  427.                 substats = '%s %s %s  %s' % (substats.rjust(7 + 2 * len(indent)), f8(tt), f8(ct), name)
  428.                 left_width = name_size + 1
  429.             else:
  430.                 substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3]))
  431.                 left_width = name_size + 3
  432.             print >>self.stream, indent * left_width + substats
  433.             indent = ' '
  434.         
  435.  
  436.     
  437.     def print_title(self):
  438.         print >>self.stream, '   ncalls  tottime  percall  cumtime  percall',
  439.         print >>self.stream, 'filename:lineno(function)'
  440.  
  441.     
  442.     def print_line(self, func):
  443.         (cc, nc, tt, ct, callers) = self.stats[func]
  444.         c = str(nc)
  445.         if nc != cc:
  446.             c = c + '/' + str(cc)
  447.         
  448.         print >>self.stream, c.rjust(9),
  449.         print >>self.stream, f8(tt),
  450.         if nc == 0:
  451.             print >>self.stream, '        ',
  452.         else:
  453.             print >>self.stream, f8(tt / nc),
  454.         print >>self.stream, f8(ct),
  455.         if cc == 0:
  456.             print >>self.stream, '        ',
  457.         else:
  458.             print >>self.stream, f8(ct / cc),
  459.         print >>self.stream, func_std_string(func)
  460.  
  461.  
  462.  
  463. class TupleComp:
  464.     '''This class provides a generic function for comparing any two tuples.
  465.     Each instance records a list of tuple-indices (from most significant
  466.     to least significant), and sort direction (ascending or decending) for
  467.     each tuple-index.  The compare functions can then be used as the function
  468.     argument to the system sort() function when a list of tuples need to be
  469.     sorted in the instances order.'''
  470.     
  471.     def __init__(self, comp_select_list):
  472.         self.comp_select_list = comp_select_list
  473.  
  474.     
  475.     def compare(self, left, right):
  476.         for index, direction in self.comp_select_list:
  477.             l = left[index]
  478.             r = right[index]
  479.             if l < r:
  480.                 return -direction
  481.             
  482.             if l > r:
  483.                 return direction
  484.                 continue
  485.         
  486.         return 0
  487.  
  488.  
  489.  
  490. def func_strip_path(func_name):
  491.     (filename, line, name) = func_name
  492.     return (os.path.basename(filename), line, name)
  493.  
  494.  
  495. def func_get_function_name(func):
  496.     return func[2]
  497.  
  498.  
  499. def func_std_string(func_name):
  500.     if func_name[:2] == ('~', 0):
  501.         name = func_name[2]
  502.         if name.startswith('<') and name.endswith('>'):
  503.             return '{%s}' % name[1:-1]
  504.         else:
  505.             return name
  506.     else:
  507.         return '%s:%d(%s)' % func_name
  508.  
  509.  
  510. def add_func_stats(target, source):
  511.     '''Add together all the stats for two profile entries.'''
  512.     (cc, nc, tt, ct, callers) = source
  513.     (t_cc, t_nc, t_tt, t_ct, t_callers) = target
  514.     return (cc + t_cc, nc + t_nc, tt + t_tt, ct + t_ct, add_callers(t_callers, callers))
  515.  
  516.  
  517. def add_callers(target, source):
  518.     '''Combine two caller lists in a single list.'''
  519.     new_callers = { }
  520.     for func, caller in target.iteritems():
  521.         new_callers[func] = caller
  522.     
  523.     for func, caller in source.iteritems():
  524.         if func in new_callers:
  525.             new_callers[func] = caller + new_callers[func]
  526.             continue
  527.         new_callers[func] = caller
  528.     
  529.     return new_callers
  530.  
  531.  
  532. def count_calls(callers):
  533.     '''Sum the caller statistics to get total number of calls received.'''
  534.     nc = 0
  535.     for calls in callers.itervalues():
  536.         nc += calls
  537.     
  538.     return nc
  539.  
  540.  
  541. def f8(x):
  542.     return '%8.3f' % x
  543.  
  544. if __name__ == '__main__':
  545.     import cmd
  546.     
  547.     try:
  548.         import readline
  549.     except ImportError:
  550.         pass
  551.  
  552.     
  553.     class ProfileBrowser(cmd.Cmd):
  554.         
  555.         def __init__(self, profile = None):
  556.             cmd.Cmd.__init__(self)
  557.             self.prompt = '% '
  558.             if profile is not None:
  559.                 self.stats = Stats(profile)
  560.                 self.stream = self.stats.stream
  561.             else:
  562.                 self.stats = None
  563.                 self.stream = sys.stdout
  564.  
  565.         
  566.         def generic(self, fn, line):
  567.             args = line.split()
  568.             processed = []
  569.             for term in args:
  570.                 
  571.                 try:
  572.                     processed.append(int(term))
  573.                 except ValueError:
  574.                     pass
  575.  
  576.                 
  577.                 try:
  578.                     frac = float(term)
  579.                     if frac > 1 or frac < 0:
  580.                         print >>self.stream, 'Fraction argument must be in [0, 1]'
  581.                         continue
  582.                     
  583.                     processed.append(frac)
  584.                 except ValueError:
  585.                     pass
  586.  
  587.                 processed.append(term)
  588.             
  589.             if self.stats:
  590.                 getattr(self.stats, fn)(*processed)
  591.             else:
  592.                 print >>self.stream, 'No statistics object is loaded.'
  593.             return 0
  594.  
  595.         
  596.         def generic_help(self):
  597.             print >>self.stream, 'Arguments may be:'
  598.             print >>self.stream, '* An integer maximum number of entries to print.'
  599.             print >>self.stream, '* A decimal fractional number between 0 and 1, controlling'
  600.             print >>self.stream, '  what fraction of selected entries to print.'
  601.             print >>self.stream, '* A regular expression; only entries with function names'
  602.             print >>self.stream, '  that match it are printed.'
  603.  
  604.         
  605.         def do_add(self, line):
  606.             self.stats.add(line)
  607.             return 0
  608.  
  609.         
  610.         def help_add(self):
  611.             print >>self.stream, 'Add profile info from given file to current statistics object.'
  612.  
  613.         
  614.         def do_callees(self, line):
  615.             return self.generic('print_callees', line)
  616.  
  617.         
  618.         def help_callees(self):
  619.             print >>self.stream, 'Print callees statistics from the current stat object.'
  620.             self.generic_help()
  621.  
  622.         
  623.         def do_callers(self, line):
  624.             return self.generic('print_callers', line)
  625.  
  626.         
  627.         def help_callers(self):
  628.             print >>self.stream, 'Print callers statistics from the current stat object.'
  629.             self.generic_help()
  630.  
  631.         
  632.         def do_EOF(self, line):
  633.             print >>self.stream, ''
  634.             return 1
  635.  
  636.         
  637.         def help_EOF(self):
  638.             print >>self.stream, 'Leave the profile brower.'
  639.  
  640.         
  641.         def do_quit(self, line):
  642.             return 1
  643.  
  644.         
  645.         def help_quit(self):
  646.             print >>self.stream, 'Leave the profile brower.'
  647.  
  648.         
  649.         def do_read(self, line):
  650.             if line:
  651.                 
  652.                 try:
  653.                     self.stats = Stats(line)
  654.                 except IOError:
  655.                     args = None
  656.                     print >>self.stream, args[1]
  657.                     return None
  658.  
  659.                 self.prompt = line + '% '
  660.             elif len(self.prompt) > 2:
  661.                 line = self.prompt[-2:]
  662.             else:
  663.                 print >>self.stream, 'No statistics object is current -- cannot reload.'
  664.             return 0
  665.  
  666.         
  667.         def help_read(self):
  668.             print >>self.stream, 'Read in profile data from a specified file.'
  669.  
  670.         
  671.         def do_reverse(self, line):
  672.             self.stats.reverse_order()
  673.             return 0
  674.  
  675.         
  676.         def help_reverse(self):
  677.             print >>self.stream, 'Reverse the sort order of the profiling report.'
  678.  
  679.         
  680.         def do_sort(self, line):
  681.             abbrevs = self.stats.get_sort_arg_defs()
  682.             if line and not filter((lambda x, a = abbrevs: x not in a), line.split()):
  683.                 self.stats.sort_stats(*line.split())
  684.             else:
  685.                 print >>self.stream, 'Valid sort keys (unique prefixes are accepted):'
  686.                 for key, value in Stats.sort_arg_dict_default.iteritems():
  687.                     print >>self.stream, '%s -- %s' % (key, value[1])
  688.                 
  689.             return 0
  690.  
  691.         
  692.         def help_sort(self):
  693.             print >>self.stream, 'Sort profile data according to specified keys.'
  694.             print >>self.stream, "(Typing `sort' without arguments lists valid keys.)"
  695.  
  696.         
  697.         def complete_sort(self, text, *args):
  698.             return _[1]
  699.  
  700.         
  701.         def do_stats(self, line):
  702.             return self.generic('print_stats', line)
  703.  
  704.         
  705.         def help_stats(self):
  706.             print >>self.stream, 'Print statistics from the current stat object.'
  707.             self.generic_help()
  708.  
  709.         
  710.         def do_strip(self, line):
  711.             self.stats.strip_dirs()
  712.             return 0
  713.  
  714.         
  715.         def help_strip(self):
  716.             print >>self.stream, 'Strip leading path information from filenames in the report.'
  717.  
  718.         
  719.         def postcmd(self, stop, line):
  720.             if stop:
  721.                 return stop
  722.             
  723.  
  724.  
  725.     import sys
  726.     if len(sys.argv) > 1:
  727.         initprofile = sys.argv[1]
  728.     else:
  729.         initprofile = None
  730.     
  731.     try:
  732.         browser = ProfileBrowser(initprofile)
  733.         print >>browser.stream, 'Welcome to the profile statistics browser.'
  734.         browser.cmdloop()
  735.         print >>browser.stream, 'Goodbye.'
  736.     except KeyboardInterrupt:
  737.         pass
  738.  
  739.  
  740.